<script setup>
/**
 * drawImage
 * 当有5个参数的时候分别代表：1，要绘制的元素 2. x轴从哪儿开始绘制 3.y轴从哪儿开始绘制 4.绘制的宽度 5.绘制的高度
 * 当有9个参数的时候分别代表：1，要绘制的元素 2. x轴从哪儿开始裁剪图片 3.y轴从哪儿开始裁剪图片 4.裁剪的宽度 5.裁剪的高度  6. x轴从哪儿开始绘制 7.y轴从哪儿开始绘制 8.绘制的宽度 9.绘制的高度
 */
import { ref, nextTick, onMounted } from "vue";
// 图片链接
const imgSrc = ref("");
// 图片元素
const imgRef = ref(null);
// canvas元素
const canvasRef = ref(null);
// mask元素
const maskRef = ref(null);
// 选区元素
const cuterRef = ref(null);
// 文件改变需要文件
const fileChange = (e) => {
  let file = e.target.files[0];
  let fr = new FileReader();
  fr.readAsDataURL(file);
  fr.onload = function () {
    imgSrc.value = fr.result;
  };
};
// 选取区域遮罩的显示开关
const cuterShow = ref(false);
// 鼠标是否可以移动
const canMove = ref(false);
// cuter距离顶部的位置
const cutY = ref(0);
// cuter距离左边的位置
const cutX = ref(0);
// 鼠标移动的距离Y
const disY = ref(0);
// 鼠标移动的距离X
const disX = ref(0);
// 限制选取范围x轴最小的距离
const limitX_min = ref(0);
// 限制选取范围x轴最大的距离
const limitX_max = ref(0);
// 限制选取范围y轴最小的距离
const limitY_min = ref(0);
// 限制选取范围y轴最大的距离
const limitY_max = ref(0);

// 开始选取按钮
const beginCuter = () => {
  // 遮罩显示
  cuterShow.value = true;
  canMove.value = true;
  // 开始的时候让鼠标位置定位在图片左上角开头位置
  cutX.value = imgRef.value.offsetLeft;
  cutY.value = imgRef.value.offsetTop;

  nextTick(() => {
    limitX_min.value = cutX.value;
    limitX_max.value =
      limitX_min.value + imgRef.value.width - cuterRef.value.offsetWidth;
    limitY_min.value = cutY.value;
    limitY_max.value =
      limitY_min.value + imgRef.value.height - cuterRef.value.offsetHeight;
  });
};
// 鼠标开始摁下
const startCuter = (e) => {
  disX.value = e.clientX - cuterRef.value.offsetLeft;
  disY.value = e.clientY - cuterRef.value.offsetTop;
};

// 节流函数（优化鼠标移动产生的性能消耗）
function throlle(fn, wait) {
    let time = Date.now()
    return function(...args){
        let newTime = Date.now()
        if(newTime - time >= wait) {
            fn.apply(this, args)
            time = newTime
        }
    }
}
// 鼠标开始移动处理事件
const moveCuterhandle = (event) => {
  if (canMove.value) {
    let _x = event.clientX - disX.value;
    let _y = event.clientY - disY.value;
    // 图片选择区域限边
    if (_x >= limitX_max.value) {
      _x = limitX_max.value;
    }
    if (_x <= limitX_min.value) {
      _x = limitX_min.value;
    }
    if (_y >= limitY_max.value) {
      _y = limitY_max.value;
    }
    if (_y <= limitY_min.value) {
      _y = limitY_min.value;
    }
    cutX.value = _x;
    cutY.value = _y;
  }
}

// 鼠标开始移动
const moveCuter = throlle(moveCuterhandle, 100);
// 鼠标抬起
const endCuter = (e) => {
  canMove.value = false;
  // 将图片绘制到canvas上
  let ctx = canvasRef.value.getContext("2d");
  // 因为现在的图片已经是等比例压缩的，所以裁剪的宽高也要等比例缩放, 最后生成的cavans的宽高已经定了
  // 裁剪的宽度等于 (最终绘制的宽度/图片现在定义的宽度)*图片原始的宽度
  let width = (200 / imgRef.value.width) * imgRef.value.naturalWidth;
  let height = (200 / imgRef.value.height) * imgRef.value.naturalHeight;

  // 这儿也要等比例缩放 裁剪的位置 = 选取位置 - 图片的offsetLeft
  let _x =
    ((cutX.value - imgRef.value.offsetLeft) / imgRef.value.width) *
    imgRef.value.naturalWidth;
  let _y =
    ((cutY.value - imgRef.value.offsetTop) / imgRef.value.height) *
    imgRef.value.naturalHeight;

  ctx.drawImage(imgRef.value, _x, _y, width, height, 0, 0, 200, 200);
  cuterShow.value = false;
};
</script>

<template>
  <!-- 选择图片 -->
  <input type="file" @change="fileChange" />
  <!-- 图片展示图片 -->
  <img v-if="imgSrc" class="img1" ref="imgRef" :src="imgSrc" alt="" />
  <!-- 裁剪后图片的展示区域 -->
  <canvas ref="canvasRef" width="200" height="200" />
  <!-- 选取区域遮罩 -->
  <div
    @mousemove="moveCuter"
    @mouseup="endCuter"
    v-show="cuterShow"
    ref="maskRef"
    class="mask"
  >
    <!-- 选取移动框 -->
    <div
      :style="{ top: cutY + 'px', left: cutX + 'px' }"
      @mousedown="startCuter"
      ref="cuterRef"
      class="cuter"
    ></div>
  </div>
  <button @click="beginCuter">开始选取</button>
</template>

<style scoped lang='less'>
.img1 {
  width: 400px;
  height: 400px;
}
.mask {
  background: rgba(0, 0, 0, 0.5);
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}
.cuter {
  width: 200px;
  height: 200px;
  border: 1px solid red;
  background: rgba(0, 0, 0, 0);
  position: absolute;
}
</style>